package compressextractor

import (
	"io"
	"io/ioutil"
	"strings"

	"gitee.com/guoliben/kydlp-doctotext/3rdparty/fileidentify"
	"gitee.com/guoliben/kydlp-doctotext/compressextractor/fileattrfilter"
	"gitee.com/guoliben/kydlp-doctotext/docextractor"
	"gitee.com/guoliben/kydlp-doctotext/extractor_interface"
	"gitee.com/guoliben/kydlp-doctotext/imageextractor"
	"gitee.com/guoliben/kydlp-doctotext/utils/ctrlledprint"
)

var (
	maxDetectDepth int
)

const (
	maxChunkSize = 1 * 1024 * 1024 // 1MB
	MAX_COMPRESS_LIMIT = 500 * 1024 * 1024 // 500MB
)

func Init(maxUncompressDepth int) error {
	maxDetectDepth = maxUncompressDepth
	return nil
}

func Destroy() {
}

func TextExtractorFactory(filetype string) extractor_interface.TextExtractor {
	switch strings.TrimSpace(filetype) {

	case fileidentify.CompressTypeTar:
		return CompressTar{}

	case fileidentify.CompressTypeGzOrTarGz:
		return CompressGz{}

	case fileidentify.CompressTypeZip:
		return CompressZip{}

	case fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2:
		return CompressRar{}

	case fileidentify.CompressType7z:
		return Compress7z{}
	}
	return extractor_interface.UnknownType{}
}

func RunTextExtractorFactory(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) (int, []byte, string) {
	switch strings.TrimSpace(filetype) {

	case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
		fileidentify.DocTypePdf,
		fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
		fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
		fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
		return docextractor.TextExtractorFactory(filetype).GetTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)

	case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
		fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
		fileidentify.CompressType7z:
		return TextExtractorFactory(filetype).GetTextFromBufferWithCallback(path, data, size, filetype, depth + 1, callback, context)

	case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
		fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
		return imageextractor.TextExtractorFactory(filetype).GetTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)

	default:
		return extractor_interface.UnknownType{}.GetTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)
	}
}

func RunStringTextExtractorFactory(path string, data []byte, size int64, filetype string, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) (int, string, string) {
	switch strings.TrimSpace(filetype) {

	case fileidentify.DocTypeText, fileidentify.DocTypeHtml, fileidentify.DocTypeXml, fileidentify.DocTypeJson,
		fileidentify.DocTypePdf,
		fileidentify.DocTypeMsDoc, fileidentify.DocTypeMsDocx,
		fileidentify.DocTypeMsXls, fileidentify.DocTypeMsXlsx,
		fileidentify.DocTypeMsPpt, fileidentify.DocTypeMsPptx:
		return docextractor.TextExtractorFactory(filetype).GetStringTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)

	case fileidentify.CompressTypeTar, fileidentify.CompressTypeGzOrTarGz, fileidentify.CompressTypeZip,
		fileidentify.CompressTypeRar, fileidentify.CompressTypeRarAliase1, fileidentify.CompressTypeRarAliase2,
		fileidentify.CompressType7z:
		return TextExtractorFactory(filetype).GetStringTextFromBufferWithCallback(path, data, size, filetype, depth + 1, callback, context)

	case fileidentify.ImageTypeBmp, fileidentify.ImageTypeJpeg, fileidentify.ImageTypePng,
		fileidentify.ImageTypeGif, fileidentify.ImageTypeTiff:
		return imageextractor.TextExtractorFactory(filetype).GetStringTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)

	default:
		return extractor_interface.UnknownType{}.GetStringTextFromBufferWithCallback(path, data, size, filetype, depth, callback, context)
	}
}

func ExtractTextWithCallback(path string, reader io.Reader, depth int, callback extractor_interface.OnTextDetectHandler, context interface{}) int {
	var retcode int
	var chunk, prevremaining []byte
	var chunksize, prevremainingsize int

	isPlaintext := false

	for {
		var curremaining []byte

		/**
		* fileidentify类型检测默认最大字节数为3K，因此这里设置buffer长度大于等于3K皆可，当然最好是2的幂次方
		*/
		buffer := make([]byte, 32 * 1024)
		n, err := reader.Read(buffer)
		if err != nil {
			if err == io.EOF {
				chunk = append(chunk, buffer[:n]...)
				break
			} else {
				ctrlledprint.Println("分块读取压缩包的子文件出错:", err)
				reader = nil
				return -1
			}
		}

		/**
		* 通过第一个内存块来探测子文件文件类型：如果是纯文本，则进行分块读取文本且当文本块大小大于等于maxChunkSize后进行分块检测；否则一次性读取读取数据并检测
		*/
		if !isPlaintext {
			filetype, extension, _, _ := fileidentify.IdentifyFromBuffer(buffer)
			if !fileattrfilter.FilterIncludeFiletype(filetype, extension) {
				return 0
			}
			if filetype == fileidentify.DocTypeText {
				isPlaintext = true
			} else {
				content, err := ioutil.ReadAll(reader)
				if err != nil {
					ctrlledprint.Println("一次性读取压缩包的子文件出错:", err)
					reader = nil
					return -1
				}
				content = append(buffer[:n], content...)

				retcode, _ , _ = RunTextExtractorFactory(path, content, int64(len(content)), filetype, depth, callback, context)

				return retcode
			}
		}

		chunk = append(chunk, buffer[:n]...)
		chunksize = len(chunk)

		if chunksize >= maxChunkSize {
			// 判断chunk的最后一个字节是否是行结束符
			lastbyte := chunk[chunksize-1]
			if lastbyte != '\r' && lastbyte != '\n' {
				// 处理不完整的行，反向读取直到遇见行结束符为止，以解决中文字符被截断导致的乱码问题
				lastNewlineIndex := -1
				for i := chunksize - 2; i >= 0; i-- {
					if chunk[i] == '\r' || chunk[i] == '\n' {
						lastNewlineIndex = i
						break
					}
				}
				if lastNewlineIndex != -1 {
					curremaining = append(curremaining, chunk[lastNewlineIndex+1:]...)
					chunk = chunk[:lastNewlineIndex+1]
					chunksize = lastNewlineIndex + 1
				} else {
					ctrlledprint.Println("当前文本块不存在行结束符!")

					curremaining = append(curremaining, chunk...)
					chunk = nil
				}
			}

			if chunk != nil {
				chunk = append(prevremaining, chunk[:chunksize]...)
				chunksize = len(chunk)

				retcode, _, _ = RunTextExtractorFactory(path, chunk, int64(chunksize), fileidentify.DocTypeText, depth, callback, context)

				chunk = nil
				prevremaining = nil

				if retcode == 0 {
					continue
				} else {
					break
				}
			}

			prevremaining = append(prevremaining, curremaining...)
		}
	}
	/**
	* 送检长度不足maxChunkSize的剩余内容（可能是最后一块纯文本，亦或是一个大小小于maxChunkSize的完整小文档）
	*/
	chunksize = len(chunk)
	prevremainingsize = len(prevremaining)
	if chunksize > 0 || prevremainingsize > 0 {
		chunk = append(prevremaining, chunk...)
		chunksize = len(chunk)

		filetype, extension, _, _ := fileidentify.IdentifyFromBuffer(chunk)
		if !fileattrfilter.FilterIncludeFiletype(filetype, extension) {
			return 0
		}
		retcode, _, _ = RunTextExtractorFactory(path, chunk, int64(chunksize), filetype, depth, callback, context)
	}

	return retcode
}

func ExtractStringTextWithCallback(path string, reader io.Reader, depth int, callback extractor_interface.OnStringTextDetectHandler, context interface{}) int {
	var retcode int
	var chunk, prevremaining []byte
	var chunksize, prevremainingsize int

	isPlaintext := false

	for {
		var curremaining []byte

		/**
		* fileidentify类型检测默认最大字节数为3K，因此这里设置buffer长度大于等于3K皆可，当然最好是2的幂次方
		*/
		buffer := make([]byte, 32 * 1024)
		n, err := reader.Read(buffer)
		if err != nil {
			if err == io.EOF {
				chunk = append(chunk, buffer[:n]...)
				break
			} else {
				ctrlledprint.Println("分块读取压缩包的子文件出错:", err)
				reader = nil
				return -1
			}
		}

		/**
		* 通过第一个内存块来探测子文件文件类型：如果是纯文本，则进行分块读取文本且当文本块大小大于等于maxChunkSize后进行分块检测；否则一次性读取读取数据并检测
		*/
		if !isPlaintext {
			filetype, extension, _, _ := fileidentify.IdentifyFromBuffer(buffer)
			if !fileattrfilter.FilterIncludeFiletype(filetype, extension) {
				return 0
			}
			if filetype == fileidentify.DocTypeText {
				isPlaintext = true
			} else {
				content, err := ioutil.ReadAll(reader)
				if err != nil {
					ctrlledprint.Println("一次性读取压缩包的子文件出错:", err)
					reader = nil
					return -1
				}
				content = append(buffer[:n], content...)

				retcode, _ , _ = RunStringTextExtractorFactory(path, content, int64(len(content)), filetype, depth, callback, context)

				return retcode
			}
		}

		chunk = append(chunk, buffer[:n]...)
		chunksize = len(chunk)

		if chunksize >= maxChunkSize {
			// 判断chunk的最后一个字节是否是行结束符
			lastbyte := chunk[chunksize-1]
			if lastbyte != '\r' && lastbyte != '\n' {
				// 处理不完整的行，反向读取直到遇见行结束符为止，以解决中文字符被截断导致的乱码问题
				lastNewlineIndex := -1
				for i := chunksize - 2; i >= 0; i-- {
					if chunk[i] == '\r' || chunk[i] == '\n' {
						lastNewlineIndex = i
						break
					}
				}
				if lastNewlineIndex != -1 {
					curremaining = append(curremaining, chunk[lastNewlineIndex+1:]...)
					chunk = chunk[:lastNewlineIndex+1]
					chunksize = lastNewlineIndex + 1
				} else {
					ctrlledprint.Println("当前文本块不存在行结束符!")

					curremaining = append(curremaining, chunk...)
					chunk = nil
				}
			}

			if chunk != nil {
				chunk = append(prevremaining, chunk[:chunksize]...)
				chunksize = len(chunk)

				retcode, _, _ = RunStringTextExtractorFactory(path, chunk, int64(chunksize), fileidentify.DocTypeText, depth, callback, context)

				chunk = nil
				prevremaining = nil

				if retcode == 0 {
					continue
				} else {
					break
				}
			}

			prevremaining = append(prevremaining, curremaining...)
		}
	}
	/**
	* 送检长度不足maxChunkSize的剩余内容（可能是最后一块纯文本，亦或是一个大小小于maxChunkSize的完整小文档）
	*/
	chunksize = len(chunk)
	prevremainingsize = len(prevremaining)
	if chunksize > 0 || prevremainingsize > 0 {
		chunk = append(prevremaining, chunk...)
		chunksize = len(chunk)

		filetype, extension, _, _ := fileidentify.IdentifyFromBuffer(chunk)
		if !fileattrfilter.FilterIncludeFiletype(filetype, extension) {
			return 0
		}
		retcode, _, _ = RunStringTextExtractorFactory(path, chunk, int64(chunksize), filetype, depth, callback, context)
	}

	return retcode
}
